package com.zackeus.sipservice;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.lifecycle.LifecycleOwner;

import com.elvishew.xlog.XLog;
import com.trello.lifecycle4.android.lifecycle.AndroidLifecycle;
import com.zackeus.common.utils.CollectionUtils;
import com.zackeus.common.utils.ObjectUtils;
import com.zackeus.common.utils.StringUtils;
import com.zackeus.mvvm.base.BaseService;
import com.zackeus.sipservice.constant.AudioCodec;
import com.zackeus.sipservice.constant.Param;
import com.zackeus.sipservice.constant.ProfileLevel;
import com.zackeus.sipservice.constant.Action;
import com.zackeus.sipservice.constant.SipStatusCode;
import com.zackeus.sipservice.constant.TransportType;
import com.zackeus.sipservice.constant.VideoCodec;
import com.zackeus.sipservice.entity.CodecPriority;
import com.zackeus.sipservice.entity.SipCallInfo;
import com.zackeus.sipservice.entity.SipRegStatus;

import org.pjsip.pjsua2.AudDevManager;
import org.pjsip.pjsua2.CallInfo;
import org.pjsip.pjsua2.CodecFmtpVector;
import org.pjsip.pjsua2.Endpoint;
import org.pjsip.pjsua2.EpConfig;
import org.pjsip.pjsua2.MediaFormatVideo;
import org.pjsip.pjsua2.TransportConfig;
import org.pjsip.pjsua2.VidCodecParam;
import org.pjsip.pjsua2.pj_qos_type;
import org.pjsip.pjsua2.pjsip_inv_state;
import org.pjsip.pjsua2.pjsip_role_e;
import org.pjsip.pjsua2.pjsip_status_code;
import org.pjsip.pjsua2.pjsip_transport_type_e;
import org.pjsip.pjsua2.pjsua_destroy_flag;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;

/**
 * SIP 服务
 * @name: SipService
 * @author: zz
 * @date: 2022/9/14
 */
public class SipService extends BaseService {

    // TODO: 2022/9/16 改为可配置
    private static final String AGENT_NAME = "AndroidSipService";
    private static final String TAG = SipService.class.getSimpleName();
    private volatile boolean mStarted = Boolean.FALSE;

    private SipServiceEventEmitter mEventEmitter;
    private Endpoint mEndpoint;
    private static final ConcurrentHashMap<String, Integer> mSupportTransports = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, SipAccount> mActiveSipAccounts = new ConcurrentHashMap<>();

    private SipReceiver mSipReceiver;

    private final int serviceId = 100;
    private static final String SERVICE_CHANNEL = "SIP-SERVICE";
    private static final String INCOMING_CHANNEL = "SIP-INCOMING";
    private int notificationIcon;
    private NotificationManagerCompat notificationManager;

    /**
     * 获取指定会话
     * @param accountId
     * @param callId
     * @return
     */
    @Nullable
    private SipCall getCall(@NonNull String accountId, int callId) {
        SipAccount account = mActiveSipAccounts.get(accountId);

        if (ObjectUtils.isNull(account)) return null;
        return account.getCall(callId);
    }

    /**
     * 构建通知
     * @param title 通知标题
     * @param text 通知文本
     */
    private NotificationCompat.Builder buildNotification(@NonNull String title, @NonNull String text) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(SipService.this, SERVICE_CHANNEL);
        builder.setPriority(NotificationCompat.PRIORITY_HIGH);
        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
        /* 设置类别 */
        builder.setCategory(NotificationCompat.CATEGORY_SERVICE);
        builder.setContentTitle(title);
        builder.setContentText(text);
        builder.setSmallIcon(notificationIcon);
        builder.setAutoCancel(Boolean.FALSE);
        return builder;
    }

    /**
     * 获取音频设备管理器
     * @return
     */
    protected synchronized AudDevManager getAudDevManager() {
        return mEndpoint.audDevManager();
    }

    /**
     * 根据传输类型获取传输器ID
     * @param tpType 传输类型
     * @return
     */
    protected int getTransportId(@TransportType.Unit String tpType) {
        Integer tpId = mSupportTransports.get(tpType);
        return ObjectUtils.isNull(tpId) ? -1 : tpId;
    }

    /**
     * 获取注册的账户
     * @return
     */
    protected ConcurrentHashMap<String, SipAccount> getActiveSipAccounts() {
        return mActiveSipAccounts;
    }

    /**
     * 获取活跃的总会话数
     * @return
     */
    protected int getTotalActiveCalls() {
        int totalCalls = 0;
        for (SipAccount _sipAccount: this.getActiveSipAccounts().values()) {
            totalCalls += _sipAccount.getActiveCallIds().size();
        }
        return totalCalls;
    }

    /**
     * 状态通知
     */
    protected void stateNotification() {
        List<String> accountIds = new ArrayList<>();
        for (Map.Entry<String, SipAccount> entry : mActiveSipAccounts.entrySet()) {
            try {
                if (entry.getValue().getInfo().getRegIsActive()) {
                    accountIds.add(entry.getValue().getData().getIdUri());
                }
            } catch (Exception e) {
                XLog.tag(TAG).w("Account status notification failed.", e);
            }
        }

        NotificationCompat.Builder builder = buildNotification("VOIP 电话正在运行", "");
        if (CollectionUtils.isNotEmpty(accountIds)) {
            /* 存在活跃账户 前台通知 */
            NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
            for (String id : accountIds) {
                inboxStyle.addLine(id);
            }
            builder.setStyle(inboxStyle);
        }
        startForeground(serviceId, builder.build());
    }

    /**
     * 取消通知
     * @param id
     */
    protected void cancelNotify(int id) {
        this.notificationManager.cancel(id);
    }

    /**
     * 获取事件广播器
     * @return
     */
    public SipServiceEventEmitter getEventEmitter() {
        return mEventEmitter;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        this.mEventEmitter = new SipServiceEventEmitter(this);

        XLog.tag(TAG).d("Creating SipService Broadcast receiver.");
        this.mSipReceiver = new SipReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Action.ACTION_INCOMING_DELETE);
        registerReceiver(mSipReceiver, filter);
        XLog.tag(TAG).d("SipService Broadcast receiver created!");

        XLog.tag(TAG).d("Creating SipService Notification.");
        notificationIcon = R.drawable.ic_voip;
        notificationManager = NotificationManagerCompat.from(SipService.this);
        /* 创建 channel */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (notificationManager.getNotificationChannel(SERVICE_CHANNEL) == null) {
                NotificationChannel channel = new NotificationChannel(SERVICE_CHANNEL, "sip服务", NotificationManager.IMPORTANCE_HIGH);
                channel.setDescription("sip服务");
                /* 开启锁屏通知 */
                channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);
                notificationManager.createNotificationChannel(channel);
            }
            if (notificationManager.getNotificationChannel(INCOMING_CHANNEL) == null) {
                NotificationChannel channel = new NotificationChannel(INCOMING_CHANNEL, "来电通知", NotificationManager.IMPORTANCE_HIGH);
                channel.setDescription("来电通知");
                /* 开启锁屏通知 */
                channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PRIVATE);
                notificationManager.createNotificationChannel(channel);
            }
        }
        XLog.tag(TAG).d("SipService Notification created!");

        XLog.tag(TAG).d("Creating SipService with priority: " + Thread.currentThread().getPriority());
        loadLibraries();

        /* 前台通知 解决 Context.startForegroundService() did not then call 异常 */
        NotificationCompat.Builder builder = buildNotification("VOIP 初始化成功", "");
        startForeground(serviceId, builder.build());

        XLog.tag(TAG).d("SipService created!");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        if (intent == null) return START_NOT_STICKY;

        String action = intent.getAction();
        if (StringUtils.isBlank(action)) return START_NOT_STICKY;

        switch (action) {
            case Action.ACTION_START_SIP_STACK:
                startStack(intent);
                break;

            case Action.ACTION_SET_ACCOUNT:
                setAccount(intent);
                break;

            case Action.ACTION_UNREGISTER_ACCOUNT:
                unregisterAccount(intent);
                break;

            case Action.ACTION_REMOVE_ACCOUNT:
                removeAccount(intent);
                break;

            case Action.ACTION_REFRESH_REGISTRATION:
                refreshRegistration(intent);
                break;

            case Action.ACTION_STOP_SIP_STACK:
                stopStack(intent);
                break;
            default: break;
        }

        return START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(@NonNull Intent intent) {
        super.onBind(intent);
        return new SipBinder();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        try {
            stopStack();
        } catch (Exception e) {
            XLog.tag(TAG).e("Error while stopping PJSIP", e);
        }

        /* 停止前台服务并删除之前的通知 */
        stopForeground(Boolean.TRUE);
        for (SipAccount _sipAccount: this.getActiveSipAccounts().values()) {
            for (Integer callId : _sipAccount.getActiveCallIds()) {
                cancelNotify(callId);
            }
        }

        /* 注销SIP广播 */
        unregisterReceiver(this.mSipReceiver);
        this.mSipReceiver = null;
    }

    private void checkState() {
        if (!this.mStarted) {
            throw new RuntimeException("SipService not start.");
        }
    }

    private SipAccount checkAccount(String accountId) {
        SipAccount sipAccount = mActiveSipAccounts.get(accountId);
        if (ObjectUtils.isNull(sipAccount)) {
            throw new RuntimeException("Account " + accountId + " not set.");
        }
        return sipAccount;
    }

    /**
     * 重置账号
     */
    private void resetAccounts() {
        XLog.tag(TAG).d("Removing all the configured accounts");

        for (Map.Entry<String, SipAccount> entry : mActiveSipAccounts.entrySet()) {
            SipAccountData data = entry.getValue().getData();

            try {
                removeAccount(data.getIdUri());
            } catch (Exception exc) {
                XLog.tag(TAG).e("Error while removing account " + data.getIdUri(), exc);
            }
        }
    }

    /**
     * 注册账户
     * @param account
     */
    private void registerAccount(SipAccountData account) throws Exception {
        String accountString = account.getIdUri();
        /* 获取当前已注册的账户 */
        SipAccount activeAccount = mActiveSipAccounts.get(accountString);

        if (activeAccount != null && activeAccount.isValid() && account.equals(activeAccount.getData())) {
            /* 账户已注册 刷新注册 */
            activeAccount.setRegistration(true);
            return;
        }

        if (activeAccount != null) {
            XLog.tag(TAG).d("Removing old account " + account.getIdUri());
            activeAccount.delete();
        }
        /* 创建新账户 */
        SipAccount sipAccount = new SipAccount(this, account);
        sipAccount.create();
        mActiveSipAccounts.put(accountString, sipAccount);
        XLog.tag(TAG).d("SIP account " + account.getIdUri() + " successfully added");
    }

    private void unregisterAccount(Intent intent) {
        String accountId = intent.getStringExtra(Param.PARAM_ACCOUNT_ID);
        try {
            unregisterAccount(accountId);
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while unregistering " + accountId, exc);
        }
    }

    /**
     * 注销账号
     * @param accountId
     */
    private void unregisterAccount(@NonNull String accountId) throws Exception {
        SipAccount account = mActiveSipAccounts.get(accountId);

        if (account == null) {
            XLog.tag(TAG).w("No account for ID: " + accountId);
            return;
        }

        XLog.tag(TAG).d("Unregistering SIP account " + accountId);
        if (account.isValid() && account.getInfo().getRegIsActive())
            /* 注销正注册的账户 */
            account.setRegistration(Boolean.FALSE);
        XLog.tag(TAG).d("SIP account " + accountId + " successfully unregistered");
    }

    private void setAccount(Intent intent) {
        SipAccountData data = intent.getParcelableExtra(Param.PARAM_ACCOUNT_DATA);
        ArrayList<CodecPriority> codecPriorities = intent.getParcelableArrayListExtra(Param.PARAM_CODEC_PRIORITIES);

        try {
            setAccount(data, codecPriorities);
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while adding " + data.getIdUri(), exc);
        }
    }

    /**
     * 设置账户并注册
     * @param sipAccountData
     * @param codecPriorities
     */
    private void setAccount(@NonNull SipAccountData sipAccountData, @Nullable ArrayList<CodecPriority> codecPriorities) throws Exception {
        checkState();
        String accountId = sipAccountData.getIdUri();

        XLog.tag(TAG).d("Adding " + accountId);
        setCodecPriorities(codecPriorities);
        registerAccount(sipAccountData);
    }

    private void removeAccount(Intent intent) {
        String accountId = intent.getStringExtra(Param.PARAM_ACCOUNT_ID);
        try {
            removeAccount(accountId);
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while removing " + accountId, exc);
        }
    }

    /**
     * 注销账号并移除
     * @param accountId
     */
    private void removeAccount(@NonNull String accountId) {
        SipAccount account = mActiveSipAccounts.remove(accountId);

        if (account == null) {
            XLog.tag(TAG).w("No account for ID: " + accountId);
            return;
        }

        XLog.tag(TAG).d("Removing SIP account " + accountId);
        account.delete();
        XLog.tag(TAG).d("SIP account " + accountId + " successfully removed");
    }

    private void setCodecPriorities(Intent intent) {
        try {
            setCodecPriorities(intent.getParcelableArrayListExtra(Param.PARAM_CODEC_PRIORITIES));
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while setting codec priorities", exc);
        }
    }

    /**
     * 设置编解码优先级
     * @param codecPriorities
     */
    private void setCodecPriorities(@Nullable ArrayList<CodecPriority> codecPriorities) throws Exception {
        checkState();
        if (codecPriorities == null)
            return;

        StringBuilder log = new StringBuilder();
        log.append("Codec priorities successfully set. The priority order is now:\n");

        for (CodecPriority codecPriority : codecPriorities) {
            mEndpoint.codecSetPriority(codecPriority.getCodecId(), (short) codecPriority.getPriority());
            log.append(codecPriority).append(",");
        }
        XLog.tag(TAG).d(log.toString());
    }

    private void refreshRegistration(Intent intent) {
        String accountId = intent.getStringExtra(Param.PARAM_ACCOUNT_ID);
        int regExpTimeout = intent.getIntExtra(Param.PARAM_REG_EXP_TIMEOUT, 0);
        try {
            refreshRegistration(accountId, regExpTimeout);
        } catch (Exception ex) {
            XLog.tag(TAG).e("Error while refreshing registration", ex);
        }
    }

    /**
     * 刷新注册
     * @param accountId
     * @param regExpTimeout
     * @throws Exception
     */
    private void refreshRegistration(@NonNull String accountId, int regExpTimeout) throws Exception {
        checkState();
        boolean refresh = Boolean.TRUE;
        SipAccount sipAccount = checkAccount(accountId);

        if (regExpTimeout != 0 && regExpTimeout != sipAccount.getData().getRegExpirationTimeout()) {
            sipAccount.getData().setRegExpirationTimeout(regExpTimeout);
            refresh = Boolean.FALSE;
        }

        if (refresh) {
            sipAccount.setRegistration(Boolean.TRUE);
        } else {
            sipAccount.modify(sipAccount.getAccountConfig());
        }
    }

    /**
     * 外呼
     * @param accountId
     * @param number
     */
    private SipCall makeCall(@NonNull String accountId, @NonNull String number) throws Exception {
        checkState();
        SipAccount sipAccount = checkAccount(accountId);

        XLog.tag(TAG).d("Making call to " + number);
        return sipAccount.outgoingCall(number);
    }

    /**
     * 应答
     * @param accountId
     * @param callId
     */
    private void answer(@NonNull String accountId, int callId) throws Exception {
        SipCall sipCall = getCall(accountId, callId);
        if (ObjectUtils.isNull(sipCall))
            return;

        sipCall.answer(pjsip_status_code.PJSIP_SC_OK, null);
    }

    /**
     * 挂断
     * @param accountId
     * @param callId
     * @throws Exception
     */
    private void hangUpCall(@NonNull String accountId, int callId) throws Exception {
        SipCall sipCall = getCall(accountId, callId);
        if (ObjectUtils.isNull(sipCall))
            return;

        int callState = sipCall.getState();
        Integer callRole = sipCall.getRole();
        if (pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED == callState || ObjectUtils.isNull(callRole))
            /* 通话已结束 忽略 */
            return;

        int statusCode = pjsip_status_code.PJSIP_SC_DECLINE;
        String reason = "NORMAL_CLEARING";
        if (pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED != callState) {
            statusCode = pjsip_role_e.PJSIP_ROLE_UAC == callRole ?
                    pjsip_status_code.PJSIP_SC_DECLINE : pjsip_status_code.PJSIP_SC_BUSY_HERE;
            reason = pjsip_role_e.PJSIP_ROLE_UAC == callRole ? "ORIGINATOR_CANCEL" : "USER_BUSY";
        }

        sipCall.hangUp(statusCode, reason);
    }

    /**
     * 挂断
     * @param accountId
     * @param callId
     * @param statusCode 拒绝状态码
     * @param reason 原因短语
     * @throws Exception
     */
    private void hangUpCall(@NonNull String accountId, int callId,
                            int statusCode, @Nullable String reason) throws Exception {
        SipCall sipCall = getCall(accountId, callId);
        if (ObjectUtils.isNotNull(sipCall)) {
            sipCall.hangUp(statusCode, reason);
        }
    }

    /**
     * 加载c库
     */
    private void loadLibraries() {
        try {
            System.loadLibrary("c++_shared");
            XLog.tag(TAG).d("libc++_shared loaded");
        } catch (UnsatisfiedLinkError error) {
            XLog.tag(TAG).e("Error while loading libc++_shared native library", error);
            throw new RuntimeException(error);
        }

        try {
            System.loadLibrary("openh264");
            XLog.tag(TAG).d("OpenH264 loaded");
        } catch (UnsatisfiedLinkError error) {
            XLog.tag(TAG).e("Error while loading OpenH264 native library", error);
            throw new RuntimeException(error);
        }

        try {
            System.loadLibrary("pjsua2");
            XLog.tag(TAG).d("PJSIP pjsua2 loaded");
        } catch (UnsatisfiedLinkError error) {
            XLog.tag(TAG).e("Error while loading PJSIP pjsua2 native library");
            throw new RuntimeException(error);
        }
    }

    private void startStack(Intent intent) {
        try {
            String iconName = intent.getStringExtra(Param.PARAM_NOTIFICATION_ICON_NAME);
            String iconType = intent.getStringExtra(Param.PARAM_NOTIFICATION_ICON_TYPE);
            if (StringUtils.isNotBlank(iconName) && StringUtils.isNotBlank(iconType)) {
                /* 自定义通知图标 */
                int icon = getResources().getIdentifier(iconName, iconType, getPackageName());
                if (icon != 0) {
                    this.notificationIcon = icon;
                } else {
                    XLog.tag(TAG).w(String.format("The notification icon: [%s:%s] does not exist.", iconName, iconType));
                }
            }

            startStack();
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while starting PJSIP", exc);
            mStarted = false;
        }
    }

    /**
     * 启动 pjsip 堆栈
     */
    private void startStack() throws Exception {

        if (mStarted) return;

        XLog.tag(TAG).d("***************** Starting PJSIP **********************");
        mEndpoint = new SipEndpoint(this);
        mEndpoint.libCreate();

        EpConfig epConfig = new EpConfig();
        /* 设置用户代理字符 */
        epConfig.getUaConfig().setUserAgent(AGENT_NAME);
        epConfig.getMedConfig().setHasIoqueue(Boolean.TRUE);
        /* 设置时钟速率 16kHZ */
        epConfig.getMedConfig().setClockRate(16000);
        /* 媒体质量 0-10 */
        epConfig.getMedConfig().setQuality(10);
        /* 回声消除设置 */
        epConfig.getMedConfig().setEcOptions(1);
        /* 回声消除器尾部长度 以毫秒为单位 0禁用 */
        epConfig.getMedConfig().setEcTailLen(200);
        /* 处理传入RTP数据包的工作线程数 */
        epConfig.getMedConfig().setThreadCnt(2);
        // TODO: 2023/2/2 设置日志 epConfig.getLogConfig
        mEndpoint.libInit(epConfig);

        TransportConfig udpTransport = new TransportConfig();
        udpTransport.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
        TransportConfig tcpTransport = new TransportConfig();
        tcpTransport.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
        TransportConfig tlsTransport = new TransportConfig();
        tlsTransport.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);

        /* 创建支持的SIP传输类型 */
        int udpTpId = mEndpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, udpTransport);
        int tcpTpId = mEndpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, tcpTransport);
        int tlsTpId = mEndpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TLS, tlsTransport);
        mSupportTransports.put(TransportType.UDP, udpTpId);
        mSupportTransports.put(TransportType.TCP, tcpTpId);
        mSupportTransports.put(TransportType.TLS, tlsTpId);

        /* 设置编解码优先级 */
        mEndpoint.codecSetPriority(AudioCodec.OPUS, (short) (CodecPriority.PRIORITY_MAX - 10));
        mEndpoint.codecSetPriority(AudioCodec.PCMA_8000, (short) (CodecPriority.PRIORITY_MAX - 11));
        mEndpoint.codecSetPriority(AudioCodec.PCMU_8000, (short) (CodecPriority.PRIORITY_MAX - 12));
        mEndpoint.codecSetPriority(AudioCodec.G729_8000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.SPEEX_8000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.SPEEX_16000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.SPEEX_32000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.GSM_8000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.G722_16000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.G7221_16000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.G7221_32000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.ILBC_8000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.AMR_WB_16000, CodecPriority.PRIORITY_DISABLED);
        mEndpoint.codecSetPriority(AudioCodec.AMR_8000, CodecPriority.PRIORITY_DISABLED);
        XLog.tag(TAG).d("Codec Priorities set!");

        // Set H264 Parameters
        VidCodecParam vidCodecParam = mEndpoint.getVideoCodecParam(VideoCodec.H264);
        CodecFmtpVector codecFmtpVector = vidCodecParam.getDecFmtp();
        MediaFormatVideo mediaFormatVideo = vidCodecParam.getEncFmt();
        mediaFormatVideo.setWidth(640);
        mediaFormatVideo.setHeight(360);
        vidCodecParam.setEncFmt(mediaFormatVideo);

        /* 设置 profile-level */
        for (int i = 0; i < codecFmtpVector.size(); i++) {
            if ("profile-level-id".equals(codecFmtpVector.get(i).getName())) {
                codecFmtpVector.get(i).setVal(ProfileLevel.CBP_31);
                break;
            }
        }
        vidCodecParam.setDecFmtp(codecFmtpVector);
        mEndpoint.setVideoCodecParam(VideoCodec.H264, vidCodecParam);

        mEndpoint.libStart();
        XLog.tag(TAG).d("********** PJSIP started! ************");
        mStarted = true;
    }

    private void stopStack(Intent intent) {
        try {
            stopStack();
        } catch (Exception exc) {
            XLog.tag(TAG).e("Error while stopping PJSIP", exc);
        }
    }

    /**
     * 停止 pjsip 堆栈
     */
    private void stopStack() throws Exception {

        if (!mStarted) return;

        try {
            XLog.tag(TAG).d("************** Stopping PJSIP *************");

            resetAccounts();
            /* 在销毁前 执行GC */
            Runtime.getRuntime().gc();

            mEndpoint.libDestroy(pjsua_destroy_flag.PJSUA_DESTROY_NO_NETWORK);
            mEndpoint.delete();

            XLog.tag(TAG).d("************ PJSIP stopped *****************");
        }  finally {
            mSupportTransports.clear();
            mActiveSipAccounts.clear();
            mStarted = false;
            mEndpoint = null;
        }
    }

    private class SipReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            XLog.tag(TAG).d("Receive a broadcast event: " + intent.getAction());

            if (Action.ACTION_INCOMING_DELETE.equals(intent.getAction())) {
                /* 来电通知取消 */
                Bundle bundle = intent.getExtras();
                if (ObjectUtils.isEmpty(bundle)) {
                    XLog.tag(TAG).e("the incomingDelete broadcast parameter is null.");
                    return;
                }

                String accountId = bundle.getString("accountId", "");
                int callId = bundle.getInt("callId", -1);
                if (StringUtils.isBlank(accountId) || callId < 0) {
                    XLog.tag(TAG).e("the incomingDelete broadcast parameter is invalid.");
                    return;
                }

                try {
                    SipService.this.hangUpCall(accountId, callId);
                } catch (Exception e) {
                    XLog.tag(TAG).e("【incomingDelete】 hangup call failed.", e);
                }
            }

        }
    }

    public class SipBinder extends Binder {

        private void checkAccount(String accountId) {
            if (StringUtils.isBlank(accountId) || !accountId.startsWith("sip:")) {
                throw new IllegalArgumentException("Invalid accountId! Example: sip:user@domain");
            }
        }

        /**
         * 获取账户注册状态
         * @param accountId sip account data
         */
        public @NonNull Observable<SipRegStatus> getRegStatus(@NonNull LifecycleOwner owner, @NonNull String accountId) {
            return Observable.create((ObservableOnSubscribe<SipRegStatus>) emitter -> {
                SipAccount activeAccount = SipService.mActiveSipAccounts.get(accountId);

                SipRegStatus sipRegStatus = new SipRegStatus(
                        accountId, Boolean.FALSE, Boolean.FALSE, 0, "", 0, Boolean.FALSE, ""
                );
                if (ObjectUtils.isNotNull(activeAccount)) {
                    sipRegStatus = new SipRegStatus(
                            activeAccount.getData().getIdUri(),
                            activeAccount.getInfo().getRegIsConfigured(),
                            activeAccount.getInfo().getRegIsActive(),
                            activeAccount.getInfo().getRegStatus(),
                            activeAccount.getInfo().getOnlineStatusText(),
                            activeAccount.getInfo().getRegExpiresSec(),
                            activeAccount.getInfo().getOnlineStatus(),
                            activeAccount.getInfo().getOnlineStatusText()
                    );
                }

                emitter.onNext(sipRegStatus);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 获取会话信息
         * @param owner
         * @param accountId
         * @param callId
         * @return
         */
        public @NonNull Observable<SipCallInfo> getCallInfo(@NonNull LifecycleOwner owner,
                                                            @NonNull String accountId, int callId) {
            return Observable.create((ObservableOnSubscribe<SipCallInfo>) emitter -> {
                SipCall call = SipService.this.getCall(accountId, callId);
                if (ObjectUtils.isNull(call)) {
                    /* 会话不存在或已销毁 */
                    emitter.onNext(new SipCallInfo(accountId));
                } else {
                    emitter.onNext(new SipCallInfo(accountId, callId, call.getRole(),
                            call.getState(), call.getInfo().getLastStatusCode(),
                            call.getInfo().getRemoteUri(), call.getConfirmedTimestamp(), call.isActive()));
                }
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        public @NonNull Observable<String> register(@NonNull LifecycleOwner owner, @NonNull SipAccountData sipAccount) {
            return register(owner, sipAccount, null);
        }

        /**
         * SIP 账号注册
         * @param owner
         * @param sipAccountData
         * @param codecPriorities 编解码优先级
         * @return
         */
        public @NonNull Observable<String> register(@NonNull LifecycleOwner owner,
                                                    @NonNull SipAccountData sipAccountData,
                                                    @Nullable ArrayList<CodecPriority> codecPriorities) {
            return Observable.create((ObservableOnSubscribe<String>) emitter -> {
                String accountId = sipAccountData.getIdUri();
                checkAccount(accountId);
                SipService.this.setAccount(sipAccountData, codecPriorities);

                emitter.onNext(accountId);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * SIP 账号注销
         * @param owner
         * @param accountId
         * @return
         */
        public @NonNull Observable<Void> unregister(@NonNull LifecycleOwner owner, @NonNull String accountId) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                checkAccount(accountId);
                SipService.this.unregisterAccount(accountId);

                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * SIP 账号注销并删除
         * 调用此方法可能不会触发 onRegistrationState 回调
         * @param owner
         * @param accountId
         * @return
         */
        public @NonNull Observable<Void> remove(@NonNull LifecycleOwner owner, @NonNull String accountId) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                checkAccount(accountId);
                SipService.this.removeAccount(accountId);

                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        public @NonNull Observable<Void> refreshRegistration(@NonNull LifecycleOwner owner, @NonNull String accountId) {
            return this.refreshRegistration(owner, accountId, 0);
        }

        /**
         * 刷新账号注册
         * @param owner
         * @param accountId
         * @param regExpTimeout
         * @return
         */
        public @NonNull Observable<Void> refreshRegistration(@NonNull LifecycleOwner owner,
                                                             @NonNull String accountId, int regExpTimeout) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                checkAccount(accountId);
                SipService.this.refreshRegistration(accountId, regExpTimeout);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 外呼
         * @param owner
         * @param accountId
         * @param dst
         * @return
         */
        public @NonNull Observable<Integer> call(@NonNull LifecycleOwner owner,
                                                 @NonNull String accountId, @NonNull String dst) {
            return Observable.create((ObservableOnSubscribe<Integer>) emitter -> {
                SipCall sipCall = SipService.this.makeCall(accountId, dst);
                emitter.onNext(sipCall.getId());
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 应答
         * @param owner
         * @param accountId
         * @param callId
         * @return
         */
        public @NonNull Observable<Void> answer(@NonNull LifecycleOwner owner, @NonNull String accountId, int callId) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipService.this.answer(accountId, callId);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 挂断
         * @param owner
         * @param accountId
         * @param callId
         * @return
         */
        public @NonNull Observable<Void> hangUp(@NonNull LifecycleOwner owner, @NonNull String accountId, int callId) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipService.this.hangUpCall(accountId, callId);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 挂断
         * @param owner
         * @param accountId
         * @param callId
         * @param statusCode 状态码
         * @param reason 原因短语
         * @return
         */
        public @NonNull Observable<Void> hangUp(@NonNull LifecycleOwner owner, @NonNull String accountId, int callId,
                                                @SipStatusCode.Unit int statusCode, @Nullable String reason) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipService.this.hangUpCall(accountId, callId, statusCode, reason);
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 设置静音
         * @param owner
         * @param accountId
         * @param callId
         * @param mute 是否静音
         * @return
         */
        public @NonNull Observable<Void> setCallMute(@NonNull LifecycleOwner owner,
                                                     @NonNull String accountId, int callId, boolean mute) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipCall sipCall = SipService.this.getCall(accountId, callId);
                if (ObjectUtils.isNotNull(sipCall)) {
                    sipCall.setMute(mute);
                }

                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        public @NonNull Observable<Void> incomingNotification(@NonNull LifecycleOwner owner,
                                                              @NonNull String accountId, int callId, Intent intent) {
            return incomingNotification(owner, accountId, callId, intent, null, null, SipService.this.notificationIcon);
        }

        public @NonNull Observable<Void> incomingNotification(@NonNull LifecycleOwner owner,
                                                              @NonNull String accountId, int callId, Intent intent, int icon) {
            return incomingNotification(owner, accountId, callId, intent, null, null, icon);
        }

        /**
         * 来电通知
         * @param owner
         * @param accountId
         * @param callId
         * @param intent 通知意图
         * @param title 通知标题
         * @param text 通知文本
         * @param icon 通知图标
         * @return
         */
        public @NonNull Observable<Void> incomingNotification(@NonNull LifecycleOwner owner,
                                                              @NonNull String accountId, int callId, Intent intent,
                                                              @Nullable String title, @Nullable String text, int icon) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipCall call = getCall(accountId, callId);
                if (ObjectUtils.isNull(call)) {
                    XLog.tag(TAG).w("No call for ID [" + accountId + " : " + callId + "]");
                    return;
                }

                CallInfo callInfo = call.getInfo();
                /* 解析 remoteUri */
                SipUri.SipContactInfo contactInfo = SipUri.parseSipContact(callInfo.getRemoteUri());
                String displayName = StringUtils.isBlank(contactInfo.displayName) ? contactInfo.userName : contactInfo.displayName;

                /* 点击意图 */
                PendingIntent contentIntent = PendingIntent.getActivity(SipService.this, callId, intent, PendingIntent.FLAG_IMMUTABLE);
                /* 删除意图 */
                Bundle bundle = new Bundle();
                bundle.putString("accountId", accountId);
                bundle.putInt("callId", callId);
                Intent dIntent = new Intent(Action.ACTION_INCOMING_DELETE);
                dIntent.putExtras(bundle);
                PendingIntent deleteIntent = PendingIntent.getBroadcast(SipService.this, callId, dIntent, PendingIntent.FLAG_IMMUTABLE);

                NotificationCompat.Builder builder = new NotificationCompat.Builder(SipService.this, INCOMING_CHANNEL);
                builder.setPriority(NotificationCompat.PRIORITY_HIGH);
                builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
                builder.setCategory(NotificationCompat.CATEGORY_CALL);
                builder.setSmallIcon(icon);
                builder.setContentTitle(StringUtils.isBlank(title) ? String.format("<%s> %s", displayName, contactInfo.userName) : title);
                builder.setContentText(StringUtils.isBlank(text) ? "邀请您语音通话" : text);
                /* 通知正在进行 */
                builder.setOngoing(Boolean.TRUE);
                /* 点击消失 */
                builder.setAutoCancel(Boolean.TRUE);
                /* 删除意图 */
                builder.setDeleteIntent(deleteIntent);
                /* 点击意图 */
                builder.setContentIntent(contentIntent);
                builder.setFullScreenIntent(contentIntent, true);

                notificationManager.notify(callId, builder.build());

                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 启动SIP栈
         * @return
         */
        public @NonNull Observable<Void> start(@NonNull LifecycleOwner owner) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                try {
                    SipService.this.startStack();
                    emitter.onComplete();
                } catch (Exception exc) {
                    mStarted = false;
                    emitter.onError(exc);
                }
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }

        /**
         * 关闭SIP栈
         */
        public @NonNull Observable<Void> stop(@NonNull LifecycleOwner owner) {
            return Observable.create((ObservableOnSubscribe<Void>) emitter -> {
                SipService.this.stopStack();
                emitter.onComplete();
            }).subscribeOn(AndroidSchedulers.mainThread()).compose(AndroidLifecycle.createLifecycleProvider(owner).bindToLifecycle());
        }
    }
}
